1   /*
2    * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.net.spi;
27  
28  import java.net.InetSocketAddress;
29  import java.net.Proxy;
30  import java.net.ProxySelector;
31  import java.net.SocketAddress;
32  import java.net.URI;
33  import java.util.ArrayList;
34  import java.util.List;
35  import java.util.StringTokenizer;
36  import java.io.IOException;
37  import sun.misc.RegexpPool;
38  import java.security.AccessController;
39  import java.security.PrivilegedAction;
40  import sun.net.NetProperties;
41  import sun.net.SocksProxy;
42  
43  /**
44   * Supports proxy settings using system properties This proxy selector
45   * provides backward compatibility with the old http protocol handler
46   * as far as how proxy is set
47   *
48   * Most of the implementation copied from the old http protocol handler
49   *
50   * Supports http/https/ftp.proxyHost, http/https/ftp.proxyPort,
51   * proxyHost, proxyPort, and http/https/ftp.nonProxyHost, and socks.
52   * NOTE: need to do gopher as well
53   */
54  public class DefaultProxySelector extends ProxySelector {
55  
56      /**
57       * This is where we define all the valid System Properties we have to
58       * support for each given protocol.
59       * The format of this 2 dimensional array is :
60       * - 1 row per protocol (http, ftp, ...)
61       * - 1st element of each row is the protocol name
62       * - subsequent elements are prefixes for Host & Port properties
63       *   listed in order of priority.
64       * Example:
65       * {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},
66       * means for FTP we try in that oder:
67       *          + ftp.proxyHost & ftp.proxyPort
68       *          + ftpProxyHost & ftpProxyPort
69       *          + proxyHost & proxyPort
70       *          + socksProxyHost & socksProxyPort
71       *
72       * Note that the socksProxy should *always* be the last on the list
73       */
74      final static String[][] props = {
75          /*
76           * protocol, Property prefix 1, Property prefix 2, ...
77           */
78          {"http", "http.proxy", "proxy", "socksProxy"},
79          {"https", "https.proxy", "proxy", "socksProxy"},
80          {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},
81          {"gopher", "gopherProxy", "socksProxy"},
82          {"socket", "socksProxy"}
83      };
84  
85      private static final String SOCKS_PROXY_VERSION = "socksProxyVersion";
86  
87      private static boolean hasSystemProxies = false;
88  
89      static {
90          final String key = "java.net.useSystemProxies";
91          Boolean b = AccessController.doPrivileged(
92              new PrivilegedAction<Boolean>() {
93                  public Boolean run() {
94                      return NetProperties.getBoolean(key);
95                  }});
96          if (b != null && b.booleanValue()) {
97              java.security.AccessController.doPrivileged(
98                        new sun.security.action.LoadLibraryAction("net"));
99              hasSystemProxies = init();
100         }
101     }
102 
103     /**
104      * How to deal with "non proxy hosts":
105      * since we do have to generate a RegexpPool we don't want to do that if
106      * it's not necessary. Therefore we do cache the result, on a per-protocol
107      * basis, and change it only when the "source", i.e. the system property,
108      * did change.
109      */
110 
111     static class NonProxyInfo {
112         // Default value for nonProxyHosts, this provides backward compatibility
113         // by excluding localhost and its litteral notations.
114         static final String defStringVal = "localhost|127.*|[::1]";
115 
116         String hostsSource;
117         RegexpPool hostsPool;
118         final String property;
119         final String defaultVal;
120         static NonProxyInfo ftpNonProxyInfo = new NonProxyInfo("ftp.nonProxyHosts", null, null, defStringVal);
121         static NonProxyInfo httpNonProxyInfo = new NonProxyInfo("http.nonProxyHosts", null, null, defStringVal);
122 
123         NonProxyInfo(String p, String s, RegexpPool pool, String d) {
124             property = p;
125             hostsSource = s;
126             hostsPool = pool;
127             defaultVal = d;
128         }
129     }
130 
131 
132     /**
133      * select() method. Where all the hard work is done.
134      * Build a list of proxies depending on URI.
135      * Since we're only providing compatibility with the system properties
136      * from previous releases (see list above), that list will always
137      * contain 1 single proxy, default being NO_PROXY.
138      */
139     public java.util.List<Proxy> select(URI uri) {
140         if (uri == null) {
141             throw new IllegalArgumentException("URI can't be null.");
142         }
143         String protocol = uri.getScheme();
144         String host = uri.getHost();
145 
146         if (host == null) {
147             // This is a hack to ensure backward compatibility in two
148             // cases: 1. hostnames contain non-ascii characters,
149             // internationalized domain names. in which case, URI will
150             // return null, see BugID 4957669; 2. Some hostnames can
151             // contain '_' chars even though it's not supposed to be
152             // legal, in which case URI will return null for getHost,
153             // but not for getAuthority() See BugID 4913253
154             String auth = uri.getAuthority();
155             if (auth != null) {
156                 int i;
157                 i = auth.indexOf('@');
158                 if (i >= 0) {
159                     auth = auth.substring(i+1);
160                 }
161                 i = auth.lastIndexOf(':');
162                 if (i >= 0) {
163                     auth = auth.substring(0,i);
164                 }
165                 host = auth;
166             }
167         }
168 
169         if (protocol == null || host == null) {
170             throw new IllegalArgumentException("protocol = "+protocol+" host = "+host);
171         }
172         List<Proxy> proxyl = new ArrayList<Proxy>(1);
173 
174         NonProxyInfo pinfo = null;
175 
176         if ("http".equalsIgnoreCase(protocol)) {
177             pinfo = NonProxyInfo.httpNonProxyInfo;
178         } else if ("https".equalsIgnoreCase(protocol)) {
179             // HTTPS uses the same property as HTTP, for backward
180             // compatibility
181             pinfo = NonProxyInfo.httpNonProxyInfo;
182         } else if ("ftp".equalsIgnoreCase(protocol)) {
183             pinfo = NonProxyInfo.ftpNonProxyInfo;
184         }
185 
186         /**
187          * Let's check the System properties for that protocol
188          */
189         final String proto = protocol;
190         final NonProxyInfo nprop = pinfo;
191         final String urlhost = host.toLowerCase();
192 
193         /**
194          * This is one big doPrivileged call, but we're trying to optimize
195          * the code as much as possible. Since we're checking quite a few
196          * System properties it does help having only 1 call to doPrivileged.
197          * Be mindful what you do in here though!
198          */
199         Proxy p = AccessController.doPrivileged(
200             new PrivilegedAction<Proxy>() {
201                 public Proxy run() {
202                     int i, j;
203                     String phost =  null;
204                     int pport = 0;
205                     String nphosts =  null;
206                     InetSocketAddress saddr = null;
207 
208                     // Then let's walk the list of protocols in our array
209                     for (i=0; i<props.length; i++) {
210                         if (props[i][0].equalsIgnoreCase(proto)) {
211                             for (j = 1; j < props[i].length; j++) {
212                                 /* System.getProp() will give us an empty
213                                  * String, "" for a defined but "empty"
214                                  * property.
215                                  */
216                                 phost =  NetProperties.get(props[i][j]+"Host");
217                                 if (phost != null && phost.length() != 0)
218                                     break;
219                             }
220                             if (phost == null || phost.length() == 0) {
221                                 /**
222                                  * No system property defined for that
223                                  * protocol. Let's check System Proxy
224                                  * settings (Gnome & Windows) if we were
225                                  * instructed to.
226                                  */
227                                 if (hasSystemProxies) {
228                                     String sproto;
229                                     if (proto.equalsIgnoreCase("socket"))
230                                         sproto = "socks";
231                                     else
232                                         sproto = proto;
233                                     Proxy sproxy = getSystemProxy(sproto, urlhost);
234                                     if (sproxy != null) {
235                                         return sproxy;
236                                     }
237                                 }
238                                 return Proxy.NO_PROXY;
239                             }
240                             // If a Proxy Host is defined for that protocol
241                             // Let's get the NonProxyHosts property
242                             if (nprop != null) {
243                                 nphosts = NetProperties.get(nprop.property);
244                                 synchronized (nprop) {
245                                     if (nphosts == null) {
246                                         if (nprop.defaultVal != null) {
247                                             nphosts = nprop.defaultVal;
248                                         } else {
249                                             nprop.hostsSource = null;
250                                             nprop.hostsPool = null;
251                                         }
252                                     }
253                                     if (nphosts != null) {
254                                         if (!nphosts.equals(nprop.hostsSource)) {
255                                             RegexpPool pool = new RegexpPool();
256                                             StringTokenizer st = new StringTokenizer(nphosts, "|", false);
257                                             try {
258                                                 while (st.hasMoreTokens()) {
259                                                     pool.add(st.nextToken().toLowerCase(), Boolean.TRUE);
260                                                 }
261                                             } catch (sun.misc.REException ex) {
262                                             }
263                                             nprop.hostsPool = pool;
264                                             nprop.hostsSource = nphosts;
265                                         }
266                                     }
267                                     if (nprop.hostsPool != null &&
268                                         nprop.hostsPool.match(urlhost) != null) {
269                                         return Proxy.NO_PROXY;
270                                     }
271                                 }
272                             }
273                             // We got a host, let's check for port
274 
275                             pport = NetProperties.getInteger(props[i][j]+"Port", 0).intValue();
276                             if (pport == 0 && j < (props[i].length - 1)) {
277                                 // Can't find a port with same prefix as Host
278                                 // AND it's not a SOCKS proxy
279                                 // Let's try the other prefixes for that proto
280                                 for (int k = 1; k < (props[i].length - 1); k++) {
281                                     if ((k != j) && (pport == 0))
282                                         pport = NetProperties.getInteger(props[i][k]+"Port", 0).intValue();
283                                 }
284                             }
285 
286                             // Still couldn't find a port, let's use default
287                             if (pport == 0) {
288                                 if (j == (props[i].length - 1)) // SOCKS
289                                     pport = defaultPort("socket");
290                                 else
291                                     pport = defaultPort(proto);
292                             }
293                             // We did find a proxy definition.
294                             // Let's create the address, but don't resolve it
295                             // as this will be done at connection time
296                             saddr = InetSocketAddress.createUnresolved(phost, pport);
297                             // Socks is *always* the last on the list.
298                             if (j == (props[i].length - 1)) {
299                                 int version = NetProperties.getInteger(SOCKS_PROXY_VERSION, 5).intValue();
300                                 return SocksProxy.create(saddr, version);
301                             } else {
302                                 return new Proxy(Proxy.Type.HTTP, saddr);
303                             }
304                         }
305                     }
306                     return Proxy.NO_PROXY;
307                 }});
308 
309         proxyl.add(p);
310 
311         /*
312          * If no specific property was set for that URI, we should be
313          * returning an iterator to an empty List.
314          */
315         return proxyl;
316     }
317 
318     public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
319         if (uri == null || sa == null || ioe == null) {
320             throw new IllegalArgumentException("Arguments can't be null.");
321         }
322         // ignored
323     }
324 
325 
326     private int defaultPort(String protocol) {
327         if ("http".equalsIgnoreCase(protocol)) {
328             return 80;
329         } else if ("https".equalsIgnoreCase(protocol)) {
330             return 443;
331         } else if ("ftp".equalsIgnoreCase(protocol)) {
332             return 80;
333         } else if ("socket".equalsIgnoreCase(protocol)) {
334             return 1080;
335         } else if ("gopher".equalsIgnoreCase(protocol)) {
336             return 80;
337         } else {
338             return -1;
339         }
340     }
341 
342     private native static boolean init();
343     private native Proxy getSystemProxy(String protocol, String host);
344 }